package com.thiswhyme.log.core;

import cn.hutool.json.JSONUtil;
import com.thiswhyme.common.web.utils.IPUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.util.StopWatch;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * controller日志切面
 * Created by thiswhyme on 2020/10/11
 */
@Slf4j
@AllArgsConstructor
@Aspect
public class LogAspect {

    private final LogPublisher logPublisher;

    //TODO 替换为真实Pointcut
    @Pointcut("execution(public * com.thiswhyme..*.*Controller.*(..))&&!@annotation(com.thiswhyme.log.core.LogIgnore)")
    public void webLog() {
    }

    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Object result = null;
        StopWatch watch = new StopWatch();
        watch.start();
        try {
            result =  joinPoint.proceed();
            watch.stop();
            return result;
        } finally {
            try {
                doLog(joinPoint, result, watch.getTotalTimeMillis());
            } catch (Exception e){
                log.error("Controller请求日志埋点失败，errMsg：{}", e.getMessage(), e);
            }
        }
    }

    /**
     * 日志处理
     * @param joinPoint
     * @param res
     * @param time
     */
    private void doLog(ProceedingJoinPoint joinPoint, Object res, long time) {
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        String apiClassMethodName = signature.getDeclaringTypeName() + "." + signature.getName();
        String reqParam = getReqParam(signature.getParameterNames(), joinPoint.getArgs());
        String resp = null == res ? "" : JSONUtil.toJsonStr(res);
        OperationLog operationLog = new OperationLog()
                .setTitle("Controller请求日志埋点")
                .setConsumingTime(time)
                .setApiClassMethodName(apiClassMethodName)
                .setReqParam(reqParam)
                .setResp(resp);

        Optional.ofNullable(RequestContextHolder.getRequestAttributes())
                .flatMap(attributes -> Optional.of(((ServletRequestAttributes) attributes).getRequest()))
                .ifPresent(request -> {
                    String ip = IPUtils.getIpAddr(request);
                    String apiUrl = request.getRequestURL().toString();
                    //TODO 替换为真实userId
                    String userId = Optional.ofNullable(request.getHeader("CurrentUserId")).orElse("0");
                    operationLog.setIp(ip)
                            .setApiUrl(apiUrl)
                            .setUserId(Long.parseLong(userId));
                });
        logPublisher.publish(operationLog);
    }

    /**
     * 获取入参
     * @param argNames 方法参数名称数组
     * @param args 方法参数数组
     * @return 返回处理后的描述
     */
    private String getReqParam(String[] argNames, Object[] args) {
        if(null == argNames || null == args) {
            return "";
        }
        Map<Object, Object> map = new HashMap<>();
        for(int i = 0; i < argNames.length; i++){
            //剔除不用打印的参数
            if(args[i] instanceof HttpServletRequest || args[i] instanceof HttpServletResponse || args[i] instanceof MultipartFile) {
                continue;
            }
            map.put(argNames[i],args[i]);
        }
        return JSONUtil.toJsonStr(map);
    }
}